package com.learning.netease.optimize.snowslide;

import com.google.common.base.Strings;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * ClassName: CacheSnowSlideService
 * Description: 缓存雪崩解决方案
 * Date: 2019/1/21 10:43 【需求编号】
 *
 * @author Sam Sho
 * @version V1.0.0
 */
@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
public class CacheSnowSlideService3 {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private com.learning.cache.redis.snowslide.DataDao dataDao;

    private final Lock lock = new ReentrantLock();

    /**
     * k/v 存储：admin -> 锁的状态
     */
    private ConcurrentHashMap<String, String> mapLock = new ConcurrentHashMap<>();

    /**
     * 锁分离：不同的 name 的锁分开
     * 如 admin -> 一把锁状态
     * 如 sam   -> 一把锁状态
     * <p>
     * 优点：
     * - 灵活多变，根据业务的需要进行调整
     * - 使用比较简单
     * <p>
     * 缺点：
     * - 降级策略的选择对于开发人员的要求高，需要能掌控业务
     * - 为了保证备份缓存的数据一致性，增加了维护的复杂度
     *
     * @param name
     * @return
     */
    public String doService(String name) {

        // 1- 先从Redis 缓存获取数据
        String result = redisTemplate.opsForValue().get(name);
        if (!Strings.isNullOrEmpty(result)) {
            log.info(Thread.currentThread().getName() + " 缓存中取得数据 ---------> " + result);
            return result;
        }

        // 2000 个线程
        boolean lock = false;
        try {
            // 返回前一个值，空的话，就是以前没有值
            lock = mapLock.putIfAbsent(name, "getLock") == null;

            // 2000个线程，一个抢到锁
            if (lock) {

                // 1-2 拿到锁以后再从缓存中取一遍，避免数据库的查询
                result = redisTemplate.opsForValue().get(name);
                if (!Strings.isNullOrEmpty(result)) {
                    log.info(Thread.currentThread().getName() + " 缓存中取得数据 ---------> " + result);
                    return result;
                }

                // 拿到锁的 重建缓存
                // 2- 缓存中没有，则取数据库
                result = dataDao.get(name);
                System.out.println(Thread.currentThread().getName() + " 从数据库中中取得数据 ============> " + result);


                // 3- 更新缓存，120秒超时
                redisTemplate.opsForValue().set(name, result, 120, TimeUnit.SECONDS);

                // 4- 备份缓存，缓存双写（数据集一致性，数据维护，独立的缓存）
//                redisTemplate.opsForValue().set(name, result);

            } else {
                // 没有拿到锁的怎么办？
                // 缓存降级：根据业务场景
                // 1- 重试，尝试争抢锁，控制重试的次数
                // 2- 返回 null 或者固定值
                // 3- 备用缓存（二级缓存）

                return "0";
            }

        } finally {
            if (lock) {
                mapLock.remove(name);
            }
        }

        return result;
    }
}
